﻿using System;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.ComponentModel;
using System.Drawing;
using RibbonControler;
using SFAR.USBHid;

namespace SFAR.RibbonControlLibrary
{
    public struct RGB
    {
        public Single R;
        public Single G;
        public Single B;

        public RGB(Single r, Single g, Single b)
        {
            R = r; G = g; B = b;
        }
    }

    public class RibbonControl : IDisposable
    {
        #region private
        private USBHid.USBHid usbHidPort;
        private AutoResetEvent receiveDataWait = new AutoResetEvent(false);
        private Byte[] receivedData = new Byte[65];
        private BackgroundWorker backgroundWriter;
        private RGB[] ledsToSend;
        #endregion

        #region Public events

        public event EventHandler OnSpecifiedDeviceArrived;
        public event EventHandler OnSpecifiedDeviceRemoved;
        public event EventHandler OnDeviceSettingsRead;
        #endregion

        #region Public Properties
        public Color[] leds;
        public int smoothingRatio = 0;
        public Color RGB;
        public uint stripLegth
        {
            set
            {
                leds = new Color[value];
                ledsToSend = new RGB[value];
            }
        }
        public uint AnalogBrightness = 120;
        public uint AnalogSaturation = 120;
        public uint DigitalBrightness = 120;
        public uint DigitalSaturation = 120;
        public bool AnalogBrightnessEnable = false;
        public bool AnalogSaturationEnable = false;
        public bool DigitalBrightnessEnable = false;
        public bool DigitalSaturationEnable = false;

        public uint firmwareVersion { get; private set; }
        public StripType stripType;
        public uint length;
        public Color startColor;


        #endregion

        public RibbonControl(uint length)
        {
            leds = new Color[length];
            ledsToSend = new RGB[length];

            usbHidPort = new USBHid.USBHid();
            usbHidPort.ProductId = 0x0003;
            usbHidPort.VendorId = 0x1FC9;
            usbHidPort.OnDeviceArrived += new EventHandler(usbHidPort_OnDeviceArrived);
            usbHidPort.OnDeviceRemoved += new EventHandler(usbHidPort_OnDeviceRemoved);
            usbHidPort.OnDataRecieved += new DataRecievedEventHandler(usbHidPort_OnDataRecieved);
            usbHidPort.OnDataSend += new EventHandler(usbHidPort_OnDataSend);
            usbHidPort.OnSpecifiedDeviceArrived += new EventHandler(usbHidPort_OnSpecifiedDeviceArrived);
            usbHidPort.OnSpecifiedDeviceRemoved += new EventHandler(usbHidPort_OnSpecifiedDeviceRemoved);

            backgroundWriter = new BackgroundWorker();
            backgroundWriter.WorkerSupportsCancellation = true;
            backgroundWriter.DoWork += new DoWorkEventHandler(backgroundWriter_DoWork);
        }

        public void Dispose()
        {
            backgroundWriter.CancelAsync();
            backgroundWriter.Dispose();
        }

        public void CheckDevicePresent()
        {
            usbHidPort.CheckDevicePresent();
        }

        public void RegisterHandle(IntPtr Handle)
        {
            usbHidPort.RegisterHandle(Handle);
        }

        public void ParseMessages(ref Message m)
        {
            usbHidPort.ParseMessages(ref m);
        }

        void usbHidPort_OnSpecifiedDeviceRemoved(object sender, EventArgs e)
        {
            if (OnSpecifiedDeviceRemoved != null)
            {
                this.OnSpecifiedDeviceRemoved(this, new EventArgs());
            }
            backgroundWriter.CancelAsync(); 
        }

        void usbHidPort_OnSpecifiedDeviceArrived(object sender, EventArgs e)
        {
            if (OnSpecifiedDeviceArrived != null)
            {
                this.OnSpecifiedDeviceArrived(this, new EventArgs());
            }
            backgroundWriter.RunWorkerAsync();
        }

        void usbHidPort_OnDataRecieved(object sender, DataRecievedEventArgs args)
        {
            if (args.data[1] == 0)
                return;
            else if (args.data[1] == 2)
            {
                Array.Copy(args.data, receivedData, args.data.Length);
                receiveDataWait.Set();
            }
            else
            {
                Debug.WriteLine(BitConverter.ToString(args.data));
            }
        }

        void usbHidPort_OnDataSend(object sender, EventArgs e)
        {
            
        }

        void usbHidPort_OnDeviceRemoved(object sender, EventArgs e)
        {
        }

        void usbHidPort_OnDeviceArrived(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// Odczyt z urządzenia danych
        /// </summary>
        /// <param name="address">Adres</param>
        /// <param name="length">Długość danych (max 58 bajtów)</param>
        /// <returns>Dane</returns>
        public Byte[] readDevice(UInt32 address, Byte length)
        {
            try
            {
                //tworzenie zapytania
                Byte[] buff = new Byte[usbHidPort.SpecifiedDevice.OutputReportLength];

                buff[1] = 2; //valid
                BitConverter.GetBytes(address).CopyTo(buff, 2); //adres
                if (length > 58)
                    length = 58;

                Byte[] outbuff = new Byte[length];

                BitConverter.GetBytes((Byte)(length | 0x80)).CopyTo(buff, 6); //ilosc danych

                //wysłanie zapytania
                receiveDataWait.Reset();
                usbHidPort.SpecifiedDevice.SendData(buff);

                //czekanie na odpowiedź
                if (!receiveDataWait.WaitOne(3000))
                {
                    Debug.WriteLine("Read timeout", "ModbusModule");
                    return null;
                }

                Array.Copy(receivedData, 7, outbuff, 0, length);

                return outbuff;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Wysłanie danych do urządzenia
        /// </summary>
        /// <param name="address">Adres</param>
        /// <param name="data">Dane (max 58 bajtów</param>
        /// <returns>Powodzenie</returns>
        public Boolean writeDevice(UInt32 address, Byte[] data)
        {
            if (data != null && data.Length > 58)
                return false;

            try
            {
                Byte[] buff = new byte[usbHidPort.SpecifiedDevice.OutputReportLength];

                buff[1] = 2; //valid
                BitConverter.GetBytes(address).CopyTo(buff, 2); //adres
                BitConverter.GetBytes((Byte)(data.Length)).CopyTo(buff, 6); //ilosc danych

                Array.Copy(data, 0, buff, 7, data.Length);

                //receiveDataWait.Reset();
                usbHidPort.SpecifiedDevice.SendData(buff);

                //if (!receiveDataWait.WaitOne(300))
                //{
                //    Debug.WriteLine("write timeout");
                //    return false;
                //}

                //buff[6] |= 0x80;

                //for (UInt32 i = 1; i < data.Length + 6; i++)
                //{
                //    if (receivedData[i] != buff[i])
                //        return false;
                //}

                return true;
            }
            catch
            {
                return false;
            }
        }

        public Boolean goToBootloader()
        {
            try
            {
                Byte[] buff = new byte[usbHidPort.SpecifiedDevice.OutputReportLength];

                buff[1] = 0xA5; //go to bootloader 
                usbHidPort.SpecifiedDevice.SendData(buff);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public Boolean sendSettings(StripType stripType, uint length, Color startColor)
        {
            try
            {
                byte[] array = new byte[8];
                array[0] = 0xFF;
                BitConverter.GetBytes(length).CopyTo(array, 1);
                array[5] = startColor.R;
                array[6] = startColor.G;
                array[7] = startColor.B;

                writeDevice(1511, array);

                return true;
            }
            catch
            {
                return false;
            }
        }

        void backgroundWriter_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            byte[] tmp = null;
            uint i;

            try
            {
                
                //odczyt wersji firmware
                firmwareVersion = BitConverter.ToUInt32(readDevice(0, 5), 0);

                tmp = readDevice(1511, 8);

                stripType = (StripType)tmp[0];

                length = BitConverter.ToUInt32(tmp, 1);
                if (length > 500)
                    length = 50;
                stripLegth = length;
                startColor = Color.FromArgb(tmp[5], tmp[6], tmp[7]);

                if (OnDeviceSettingsRead != null)
                    OnDeviceSettingsRead(this, EventArgs.Empty);

                tmp = new byte[58];
            }
            catch
            {
            }

            for (; ; )
            {
                if (worker.CancellationPending == true)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    //oblicz nowy kolor
                    if (smoothingRatio > 0)
                    {
                        for (i = 0; i < ledsToSend.Length; i++)
                        {
                            ledsToSend[i] = new RGB(
                                (smoothingRatio * ledsToSend[i].R + leds[i].R) / (smoothingRatio + 1),
                                (smoothingRatio * ledsToSend[i].G + leds[i].G) / (smoothingRatio + 1),
                                (smoothingRatio * ledsToSend[i].B + leds[i].B) / (smoothingRatio + 1));
                        }
                    }
                    else
                    {
                        for (i = 0; i < ledsToSend.Length; i++)
                        {
                            ledsToSend[i] = new RGB(leds[i].R, leds[i].G, leds[i].B);
                        }
                    }

                    HSLColor color;

                    if (DigitalBrightnessEnable || DigitalSaturationEnable)
                    {
                        for (i = 0; i < ledsToSend.Length; i++)
                        {
                            color = Color.FromArgb(leds[i].R, leds[i].G, leds[i].B);
                            if (DigitalBrightnessEnable)
                                color.Luminosity *= DigitalBrightness / 120.0;
                            if (DigitalSaturationEnable)
                                color.Saturation = DigitalSaturation / 240.0;
                            Color rgb = color;
                            ledsToSend[i] = new RGB(rgb.R, rgb.G, rgb.B);
                        }
                    }
                    else
                    {
                        for (i = 0; i < ledsToSend.Length; i++)
                        {
                            ledsToSend[i] = new RGB(leds[i].R, leds[i].G, leds[i].B);
                        }
                    }

                    int size = leds.Length * 3 + 7;
                    if (size % 58 != 0)
                    {
                        size = (size / 58 + 1) * 58;
                    }
                    byte[] array = new byte[size];

                    BitConverter.GetBytes(leds.Length).CopyTo(array, 0);

                    if (AnalogBrightnessEnable || AnalogSaturationEnable)
                    {    
                        color = new HSLColor(RGB.R, RGB.G, RGB.B);
                        if (AnalogBrightnessEnable)
                            color.Luminosity *= AnalogBrightness / 120.0;
                        if (AnalogSaturationEnable)
                            color.Saturation = AnalogSaturation;
                        Color rgb = color;
                        array[4] = rgb.R;
                        array[5] = rgb.G;
                        array[6] = rgb.B;
                    }
                    else
                    {
                        array[4] = RGB.R;
                        array[5] = RGB.G;
                        array[6] = RGB.B;
                    }
                    

                    for (i = 0; i < leds.Length; i++)
                    {
                        array[7 + i * 3] = (byte)ledsToSend[i].R;
                        array[7 + i * 3 + 1] = (byte)ledsToSend[i].G;
                        array[7 + i * 3 + 2] = (byte)ledsToSend[i].B;
                    }

                    for (i = 0; i < array.Length; i += 58)
                    {
                        Array.Copy(array, i, tmp, 0, 58);
                        writeDevice(4 + i, tmp);
                    }

                    Thread.Sleep(1);
                }
            }
        }
    }

    public enum StripType
    {
        UCS1903 = 0xFF,
        TM1804 = 0xFE

    }
}
